/*
 * Copyrignt (c) xuzeshui.com. All Rights Reserved.
 * Author: Zeshui Xu<i@xuzeshui.com>
 * Created Time: 2015-12-10 14:06:56
 * Last Modified: 2015-12-17 15:50:36
 * File Name: xfile/xfile.go
 * Description:
 */
package xfile

import (
	"bufio"
	"fmt"
	"os"
	"sync"
	"time"

	log "github.com/cihub/seelog"
)

const (
	BUFFER_WRITE_SIZE int = 10 * 1024 * 1024
)

type rollingFileWriter struct {
	saveDirPath     string        //文件保存目录
	filenamePrefix  string        //文件前缀
	rollingInterval time.Duration //滚动时间
	currentFile     *os.File
	currentFilePath string        //当前的文件完整路径
	bufw            *bufio.Writer //带buffer的缓冲写
	writeMutex      *sync.Mutex
}

func NewRollingFileWriter(saveDirPath, fnPrefix string, rollingInterval int) (*rollingFileWriter, error) {
	rw := new(rollingFileWriter)
	rw.saveDirPath = saveDirPath
	rw.filenamePrefix = fnPrefix
	rw.rollingInterval = time.Duration(time.Duration(rollingInterval) * time.Second)
	if rw.saveDirPath == "" {
		rw.saveDirPath = "."
	}
	rw.currentFile = nil
	rw.bufw = nil
	rw.writeMutex = new(sync.Mutex)
	if rollingInterval != 0 {
		go rw.flushPeriodically()
	}
	return rw, nil
}

func (p *rollingFileWriter) createFileAndFolderIfNeeded() error {
	p.writeMutex.Lock()
	defer p.writeMutex.Unlock()
	if p.currentFile == nil {
		if len(p.saveDirPath) != 0 {
			if err := os.MkdirAll(p.saveDirPath, 0777); err != nil {
				log.Warnf("mkdir for %s error, msg: %s", p.saveDirPath, err.Error())
				return err
			}
		}
		p.currentFilePath = fmt.Sprintf("%s/%s.%s.%d", p.saveDirPath, p.filenamePrefix, time.Now().Format("20060102150405"), os.Getpid())
		if _, err := os.Lstat(p.currentFilePath); err == nil {
			p.currentFile, _ = os.OpenFile(p.currentFilePath, os.O_WRONLY|os.O_APPEND, 0777)
		} else {
			p.currentFile, _ = os.Create(p.currentFilePath)
		}
		p.bufw = bufio.NewWriterSize(p.currentFile, BUFFER_WRITE_SIZE)
	}
	return nil
}

func (p *rollingFileWriter) Write(bytes []byte) (int, error) {
	if err := p.createFileAndFolderIfNeeded(); err != nil {
		return 0, err
	}

	p.writeMutex.Lock()
	defer p.writeMutex.Unlock()
	return p.bufw.Write(bytes)
}

func (p *rollingFileWriter) Close() error {
	if p.currentFile == nil {
		return nil
	}
	p.writeMutex.Lock()
	defer p.writeMutex.Unlock()
	p.bufw.Flush()
	p.currentFile.Close()
	p.currentFile = nil
	p.bufw = nil
	return nil
}

func (p *rollingFileWriter) flushAllAndRolling() {
	if p.currentFile == nil {
		return
	}
	p.writeMutex.Lock()
	defer p.writeMutex.Unlock()

	go func(fi *os.File, w *bufio.Writer) {
		w.Flush()
		fi.Close()
	}(p.currentFile, p.bufw)

	p.currentFile = nil
	p.bufw = nil
}

func (p *rollingFileWriter) flushPeriodically() {
	if p.rollingInterval > 0 {
		for {
			select {
			case <-time.After(p.rollingInterval):
				//log.Debugf("flushPeriodically......")
				p.flushAllAndRolling()
			}
		}
	}
}
